#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/suspend.h>
#include <linux/genalloc.h>
#include <asm/fncpy.h>
#include <asm/cacheflush.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <asm/suspend.h>
#include <linux/of_device.h>
#include "mach/pmu.h"


typedef void (*fh_suspend_sram_fn_cb)(void *p1, void *p2);
static fh_suspend_sram_fn_cb fh_suspend_sram_fn;

//static void (*fh_suspend_sram_fn)(void *p1,void *p2);
static suspend_state_t target_state;
static void __iomem *ddrc_adr;
static void __iomem *dpll_adr;
static struct fh_pm_plat *pm_plat;

static int fh_pm_valid_state(suspend_state_t state)
{
	switch (state) {
	case PM_SUSPEND_ON:
	case PM_SUSPEND_STANDBY:
	case PM_SUSPEND_MEM:
		return 1;
	default:
		return 0;
	}
}

static int fh_lpmode_enter(unsigned long arg)
{
	flush_cache_all();
	if (pm_plat && pm_plat->suspend_fn)
		pm_plat->suspend_fn();
#ifdef CONFIG_FH_DDR_SUSPEND
	fh_suspend_sram_fn(ddrc_adr, dpll_adr);
#else
	wfi();
#endif

	if (pm_plat && pm_plat->resume_fn)
		pm_plat->resume_fn();
	pr_err("%s: cpu_resume\n", __func__);

	return 0;
}

static int fh_pm_enter(suspend_state_t state)
{
	int ret = 0;

	pr_info("fh_pm_enter in %x\n", state);

	switch (state) {
	case PM_SUSPEND_ON:
		//fh_suspend_sram_fn(ddrc_adr);
		break;
	case PM_SUSPEND_STANDBY:
		//fh81_pm_suspend_to_cache();
		break;
	case PM_SUSPEND_MEM:
		cpu_suspend(0, fh_lpmode_enter);
		break;
	default:
		ret = -EINVAL;
	}
	pr_info("fh_pm_enter out %x\n", state);
	return ret;
}

static int fh_pm_begin(suspend_state_t state)
{
	pr_info("fh_pm_begin %x\n", state);
	target_state = state;
	return 0;
}

static void fh_pm_end(void)
{
	pr_info("fh_pm_end\n");
	target_state = PM_SUSPEND_ON;
}

static const struct platform_suspend_ops fh_pm_ops = {
	.enter = fh_pm_enter,
	.valid = fh_pm_valid_state,
	.begin = fh_pm_begin,
	.end = fh_pm_end,
};

extern void fh_pm_suspend_in_sram(void *p);
extern u32 fh_pm_suspend_in_sram_sz;

static int __init fh_pm_probe(struct platform_device *pdev)
{
	int status = 0;
	void __iomem  *addr;
	u32 ram_base;
	u32 reg;

#ifdef CONFIG_OF
	device_property_read_u32(&pdev->dev, "reg", &reg);
	ram_base = reg;
#else
	pm_plat = pdev->dev.platform_data;
	ram_base = pm_plat->rambase;
#endif
#ifdef CONFIG_FH_DDR_SUSPEND
	ddrc_adr = ioremap(pm_plat->addr1, SZ_4K);
	dpll_adr = ioremap(pm_plat->addr2, SZ_4K);
	pr_info("ddrc_adr:0x%08x\n", ddrc_adr);
	pr_info("dpll_adr:0x%08x\n", dpll_adr);

	pr_info("ram_base:0x%08x\n", ram_base);
	if (ram_base) {
		addr = __arm_ioremap_exec(ram_base, SZ_64K, false);
		if (!addr) {
			pr_err("iomap fail\n");
			return -ENOMEM;
		}
	}
	fh_suspend_sram_fn = addr;
	/* Copy the pm suspend handler to SRAM */
	fh_suspend_sram_fn = (fh_suspend_sram_fn_cb)fncpy(fh_suspend_sram_fn,
			&fh_pm_suspend_in_sram, fh_pm_suspend_in_sram_sz);
#endif
	suspend_set_ops(&fh_pm_ops);
	return 0;
}

static int __exit fh_pm_remove(struct platform_device *pdev)
{
	return 0;
}

static struct of_device_id const fh_pm_of_match[] = {
	{ .compatible = "fh,fh-pm", },
	{ }
};

static struct platform_driver fh_pm_driver = {
	.driver = {
		.name = "fh-pm",
		.of_match_table = fh_pm_of_match,
	},
	.probe = fh_pm_probe,
	.remove = __exit_p(fh_pm_remove),
};

int __init fh_pm_init(void)
{
	return platform_driver_register(&fh_pm_driver);
}

subsys_initcall(fh_pm_init);
